Wednesday Code and Notes (Week 4)
Table of Contents
1 Java code
1.1 Dining philosophers, first attempt
import java.util.concurrent.Semaphore; class Philosopher extends Thread { String name; Semaphore left_fork; Semaphore right_fork; Philosopher(String name, Semaphore left_fork, Semaphore right_fork) { this.name = name; this.left_fork = left_fork; this.right_fork = right_fork; } public void run () { while(true) { System.out.println(name + " thinks..."); try { Thread.sleep(150); } catch(InterruptedException e) { } left_fork.acquireUninterruptibly(); System.out.println(name + " grabbed a fork..."); try { Thread.sleep(150); } catch(InterruptedException e) { } right_fork.acquireUninterruptibly(); System.out.println(name + " grabbed another fork..."); System.out.println(name + " eating..."); try { Thread.sleep(150); } catch(InterruptedException e) { } System.out.println(name + " releases forks..."); left_fork.release(); right_fork.release(); } } } public class Dinphil { public static void main(String[] args) { // we want binary semaphores that have 1 free ticket initially // we don't care about liveness here, only deadlock Semaphore fork1 = new Semaphore(1,false); Semaphore fork2 = new Semaphore(1,false); Semaphore fork3 = new Semaphore(1,false); Thread t1 = new Philosopher("Hegel",fork1,fork2); Thread t2 = new Philosopher("Plato",fork2,fork3); Thread t3 = new Philosopher("Averroes",fork3,fork1); t1.start(); t2.start(); t3.start(); } } /* We're deadlocked :( Suggestion: - make "grab both forks" a critical section with another semaphore, shared between everybody - ordering the forks In the system as a whole, we'll have n locks (or semaphores) each process requires some subset of the locks Globally order all the locks Total order: fork1 < fork2 < fork3 Enforce discipline: everybody needs to claim the "smallest" lock first If everybody claims locks in an order consistent w/ the total order, deadlock is impossible. If P is stuck, that means somebody is hogging a higher lock. But the process hogging the *highest* lock can't be stuck. */
1.2 Dining philosophers, second attempt
This solution fixes the deadlock issue by enforcing a total order on locks.
import java.util.concurrent.Semaphore; class Philosopher extends Thread { String name; Semaphore left_fork; Semaphore right_fork; Philosopher(String name, Semaphore left_fork, Semaphore right_fork) { this.name = name; this.left_fork = left_fork; this.right_fork = right_fork; } public void run () { while(true) { System.out.println(name + " thinks..."); try { Thread.sleep(150); } catch(InterruptedException e) { } left_fork.acquireUninterruptibly(); System.out.println(name + " grabbed a fork..."); try { Thread.sleep(150); } catch(InterruptedException e) { } right_fork.acquireUninterruptibly(); System.out.println(name + " grabbed another fork..."); System.out.println(name + " eating..."); try { Thread.sleep(150); } catch(InterruptedException e) { } System.out.println(name + " releases forks..."); left_fork.release(); right_fork.release(); } } } public class Dinphil2 { public static void main(String[] args) { // we want binary semaphores that have 1 free ticket initially // we don't care about liveness here, only deadlock Semaphore fork1 = new Semaphore(1,false); Semaphore fork2 = new Semaphore(1,false); Semaphore fork3 = new Semaphore(1,false); // fork1 < fork2 < fork3 Thread t1 = new Philosopher("Hegel",fork1,fork2); Thread t2 = new Philosopher("Plato",fork2,fork3); Thread t3 = new Philosopher("Averroes",fork1,fork3); t1.start(); t2.start(); t3.start(); } }
1.3 Rendezvous, first attempt
import java.util.concurrent.Semaphore; class RendezvousThread extends Thread { String name; public RendezvousThread(String name) { this.name = name; } public void run() { System.out.println(name + " first statement"); System.out.println(name + " second statement"); } } public class Rendezvous { public static void main(String[] args) { Thread t1 = new RendezvousThread("Bertram"); Thread t2 = new RendezvousThread("Agatha"); t1.start(); t2.start(); } }
1.4 Rendezvous, second attempt
import java.util.concurrent.Semaphore; class RendezvousThread extends Thread { String name; Semaphore s1; Semaphore s2; public RendezvousThread(String name, Semaphore s1, Semaphore s2) { this.name = name; this.s1 = s1; this.s2 = s2; } public void run() { System.out.println(name + " first statement"); // here, we want to: unblock the other proc // wait until the other proc unblocks us s1.release(); s2.acquireUninterruptibly(); System.out.println(name + " second statement"); } } public class Rendezvous2 { public static void main(String[] args) { Semaphore s1 = new Semaphore(0); Semaphore s2 = new Semaphore(0); Thread t1 = new RendezvousThread("Bertram",s1,s2); Thread t2 = new RendezvousThread("Agatha",s2,s1); t1.start(); // start means // execute run() in a new thread t2.start(); } }
1.5 Rendezvous, attempt 3
Here's a hack we can do to achieve the same thing with one Java semaphore, because of the oddity that we can have initially negative number of permits.
import java.util.concurrent.Semaphore; class RendezvousThread extends Thread { String name; Semaphore s1; public RendezvousThread(String name, Semaphore s1) { this.name = name; this.s1 = s1; } public void run() { System.out.println(name + " first statement"); // here, we want to: unblock the other proc // wait until the other proc unblocks us s1.release(); s1.acquireUninterruptibly(); System.out.println(name + " second statement"); s1.release(); } } public class Rendezvous3 { public static void main(String[] args) { Semaphore s1 = new Semaphore(-1); Thread t1 = new RendezvousThread("Bertram",s1); Thread t2 = new RendezvousThread("Agatha",s1); t1.start(); // start means // execute run() in a new thread t2.start(); } }
2 Notes
Locks:
- lock() acquire() --- pre-protocol
unlock() release() --- post-protocol
Locks: only one process can claim the lock at one time.
Semaphores:
- multiple process can pass through a semaphore at the same time.
Binary semaphores: (v,L)
- v number of free process slots, free tickets
- L processes on waiting list
For binary semaphores, v is always either 0 or 1.
A lock is a special case of a semaphore, namely a binary semaphore.
wait signal
acronyms P V
dutch passering vrijgaven
java acquire release
Difference:
- when we signal, we unblocke someone from the waiting set.
But: who gets unblocked? And to where do they get unblocked?
busy-wait semaphores
aka very weak semaphores
- You're unblocked to the state *before* the wait.
You have to try another wait.
This means you can be preempted by people who weren't
on the waiting list when the signal came.
- Who gets unblocked? Any member of L.
weak semaphores
- When a signal happens, one process, any process, is unblocked.
- You're released to the state *after* the wait.
That is: you're released into the "critical section",
and don't have to retry the wait.
strong semaphores
- Processes are released in FIFO order.
- You're released to the state *after* the wait.
That is: you're released into the "critical section",
and don't have to retry the wait.
Q: What impact does weak vs. busy-wait have on eventual entry?
(for 2 processes)
Busy-wait (aka very weak) doesn't guarantee eventual entry.
Weak semaphores do guarantee eventual entry.
Q: What about for 3 or more procs?
Weak semaphores don't guarantee eventual entry.
If you're unlucky, every signal happens when you're sharing the
waiting room with someone else, who push ahead.
Q: but what about under weak fairness?
Still no :(
Weak fairness guarantees that if an action is *always* enabled,
then it will eventually happen.
(If a process is always not blocked, it will eventually
make progress).
But: you're not *always* unblocked.
You're *always eventually* unblocked.
Suppose p1 is on the waiting list.
p2 and p3 are forever taking turns entering the CS.
p1 will:
- sit on the wait list (be blocked)
- be unblocked whenever anyone's about to execute the pre-protocol.
(unblocked: you could possibly be chosen to go ahead)
- in the next state, you're blocked again.
- repeat ad infinitum
Q: what about under strong fairness?
A: Yes, strong fairness is enough (see above).
(1) v = 1 + #signal(S) - #wait(S)
(2) v ≥ 0
(3) #CS = #wait(S) - #signal(S)
Substitute (1) into (2)
1 + #signal(S) - #wait(S) ≥ 0
(add and subtract on both sides)
1 ≥ #wait(s) - #signal(S) = #CS
#CS ≤ 1
